//
//  LibyuvColorSpaceConvert.cpp
//  MediaStreamer
//
//  Created by Think on 16/2/14.
//  Copyright © 2016年 Cell. All rights reserved.
//

#include "LibyuvColorSpaceConvert.h"
#include "libyuv.h"
#include <stddef.h>
//#include "MediaLog.h"

LibyuvColorSpaceConvert::LibyuvColorSpaceConvert()
{
    int max_video_frame_size = MAX_VIDEO_FRAME_SIZE;
    mI420 = new MediaStreamer::VideoFrame;
    mI420->data = (uint8_t*)malloc(max_video_frame_size);
    mI420->frameSize = max_video_frame_size;
    
    mI420UnMirror = new MediaStreamer::VideoFrame;
    mI420UnMirror->data = (uint8_t*)malloc(max_video_frame_size);
    mI420UnMirror->frameSize = max_video_frame_size;
}

LibyuvColorSpaceConvert::~LibyuvColorSpaceConvert()
{
    if (mI420!=NULL) {
        if (mI420->data!=NULL) {
            free(mI420->data);
            mI420->data = NULL;
        }
        
        delete mI420;
        mI420 = NULL;
    }
    
    if (mI420UnMirror!=NULL) {
        if (mI420UnMirror->data!=NULL) {
            free(mI420UnMirror->data);
            mI420UnMirror->data = NULL;
        }
        
        delete mI420UnMirror;
        mI420UnMirror = NULL;
    }
}

bool LibyuvColorSpaceConvert::RGBAtoI420(MediaStreamer::VideoFrame* rgba, MediaStreamer::VideoFrame* i420)
{
    //i420->frameSize = i420->width*i420->height*3/2;
    i420->pts = rgba->pts;
    
    //    uint8* src_y = nv12->data;
    //    int src_stride_y = nv12->width;
    //    uint8* src_uv = src_y + src_stride_y * nv12->height;
    //    int src_stride_uv = nv12->width;
    uint8* src_frame = rgba->data;
    int src_stride_frame = rgba->frameSize;
    
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    
    int ret = libyuv::RGBAToI420(src_frame, src_stride_frame,
                                 dst_y, dst_stride_y,
                                 dst_u, dst_stride_u,
                                 dst_v, dst_stride_v,
                                 i420->width, i420->height);
    
    if (ret) return false;
    
    return true;
}

//int RGBAToI420(const uint8* src_frame, int src_stride_frame,
//               uint8* dst_y, int dst_stride_y,
//               uint8* dst_u, int dst_stride_u,
//               uint8* dst_v, int dst_stride_v,
//               int width, int height);

bool LibyuvColorSpaceConvert::ARGBtoI420(MediaStreamer::VideoFrame* argb, MediaStreamer::VideoFrame* i420)
{
    //i420->frameSize = i420->width*i420->height*3/2;
    i420->pts = argb->pts;
    
    //    uint8* src_y = nv12->data;
    //    int src_stride_y = nv12->width;
    //    uint8* src_uv = src_y + src_stride_y * nv12->height;
    //    int src_stride_uv = nv12->width;
    uint8* src_frame = argb->data;
    int src_stride_frame = argb->width*4;
    
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    
    //BGRAToI420  //error
    //ABGRToI420
    //ARGBToI420 //err
    int ret = libyuv::ABGRToI420(src_frame, src_stride_frame,
                                 dst_y, dst_stride_y,
                                 dst_u, dst_stride_u,
                                 dst_v, dst_stride_v,
                                 i420->width, i420->height);
    
    if (ret) return false;
    
    return true;

}

bool LibyuvColorSpaceConvert::NV12toI420(MediaStreamer::VideoFrame* nv12, MediaStreamer::VideoFrame* i420)
{
    i420->frameSize = nv12->frameSize;
    i420->pts = nv12->pts;
    
    uint8* src_y = nv12->data;
    int src_stride_y = nv12->width;
    uint8* src_uv = src_y + src_stride_y * nv12->height;
    int src_stride_uv = nv12->width;
    
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    
    int ret = libyuv::NV12ToI420(src_y, src_stride_y,
                                 src_uv, src_stride_uv,
                                 dst_y, dst_stride_y,
                                 dst_u, dst_stride_u,
                                 dst_v, dst_stride_v,
                                 i420->width, i420->height);
    
    if (ret) return false;
    
    return true;
}

bool LibyuvColorSpaceConvert::NV12toI420_Rotation(MediaStreamer::VideoFrame* nv12, MediaStreamer::VideoFrame* i420, int rotation)
{
    mI420->frameSize = nv12->frameSize;
    mI420->pts = nv12->pts;
    mI420->width = nv12->width;
    mI420->height = nv12->height;
    
    this->NV12toI420(nv12, mI420);
    
    uint8 *src_y = mI420->data;
    int src_stride_y = mI420->width;
    uint8 *src_u = src_y + src_stride_y * mI420->height;
    int src_stride_u = (mI420->width+1)/2;
    uint8 *src_v = src_u + src_stride_u * mI420->height/2;
    int src_stride_v = (mI420->width+1)/2;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int src_width = mI420->width;
    int src_height = mI420->height;
    
    i420->frameSize = mI420->frameSize;
    i420->pts = mI420->pts;
    
    int ret = libyuv::I420Rotate(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, src_width, src_height, libyuv::RotationMode(rotation));
    
    if (ret) return false;
    
    return true;
}


bool LibyuvColorSpaceConvert::NV12toI420_Crop_Rotation(MediaStreamer::VideoFrame* nv12, MediaStreamer::VideoFrame* i420, int rotation)
{
    i420->pts = nv12->pts;
    
    int i420_width_pre_rotation= 0;
    int i420_height_pre_rotation = 0;
    
    if (rotation==0 || rotation==180) {
        i420_width_pre_rotation = i420->width;
        i420_height_pre_rotation = i420->height;
    }else if(rotation==90 || rotation==270) {
        i420_width_pre_rotation = i420->height;
        i420_height_pre_rotation = i420->width;
    }
    
    if (nv12->width<i420_width_pre_rotation || nv12->height<i420_height_pre_rotation) {
        return false;
    }
    
    uint8* src_frame = nv12->data;
    size_t src_size = nv12->frameSize;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int crop_x;
    int crop_y;
    int src_width = nv12->width;
    int src_height = nv12->height;
    int crop_width;
    int crop_height;
    uint32 format = libyuv::FOURCC_NV12;
    
    if (rotation==0 || rotation==180) {
        crop_x = (nv12->width - i420->width)/2;
        crop_y = (nv12->height - i420->height)/2;
        
        crop_width = i420->width;
        crop_height = i420->height;
    }else if(rotation==90 || rotation==270) {
        crop_x = (nv12->width - i420->height)/2;
        crop_y = (nv12->height - i420->width)/2;
        
        crop_width = i420->height;
        crop_height = i420->width;
    }else {
        crop_x = 0;
        crop_y = 0;
        
        crop_width = src_width;
        crop_height = src_height;
    }
    
    int ret = libyuv::ConvertToI420(src_frame, src_size,
                                    dst_y, dst_stride_y,
                                    dst_u, dst_stride_u,
                                    dst_v, dst_stride_v,
                                    crop_x, crop_y,
                                    src_width, src_height,
                                    crop_width, crop_height,
                                    libyuv::RotationMode(rotation),
                                    format);
    
    if (ret) return false;
    
    return true;
}


bool LibyuvColorSpaceConvert::NV12toI420_Crop_Rotation_Scale(MediaStreamer::VideoFrame* nv12, MediaStreamer::VideoFrame* i420, int rotation)
{
    i420->pts = nv12->pts;
    
    int i420_width_pre_rotation= 0;
    int i420_height_pre_rotation = 0;
    
    if (rotation==0 || rotation==180) {
        i420_width_pre_rotation = i420->width;
        i420_height_pre_rotation = i420->height;
    }else if(rotation==90 || rotation==270) {
        i420_width_pre_rotation = i420->height;
        i420_height_pre_rotation = i420->width;
    }
    
    //    if (nv21->width>=i420_width_pre_rotation && nv21->height>=i420_height_pre_rotation) {
    //        return NV21toI420_Crop_Rotation(nv21, i420, rotation);
    //    }
    
    if(nv12->width*i420_height_pre_rotation>=i420_width_pre_rotation*nv12->height)
    {
        if (rotation==0 || rotation==180) {
            mI420->width = i420_width_pre_rotation*nv12->height/i420_height_pre_rotation;
            mI420->height = nv12->height;
        }else if(rotation==90 || rotation==270) {
            mI420->width = nv12->height;
            mI420->height = i420_width_pre_rotation*nv12->height/i420_height_pre_rotation;
        }
        
    }else if(nv12->width*i420_height_pre_rotation<i420_width_pre_rotation*nv12->height)
    {
        if (rotation==0 || rotation==180) {
            mI420->width = nv12->width;
            mI420->height = i420_height_pre_rotation*nv12->width/i420_width_pre_rotation;
        }else if(rotation==90 || rotation==270) {
            mI420->width = i420_height_pre_rotation*nv12->width/i420_width_pre_rotation;
            mI420->height = nv12->width;
        }
    }
    mI420->width = mI420->width%2>0?mI420->width+1:mI420->width;
    mI420->height = mI420->height%2>0?mI420->height+1:mI420->height;
    
    mI420->frameSize = mI420->width*mI420->height*3/2;
    mI420->pts = nv12->pts;
    
    bool bRet = NV12toI420_Crop_Rotation(nv12, mI420, rotation);
    
    if (!bRet) return false;
    
    //do scale
    uint8 *src_y = mI420->data;
    int src_stride_y = mI420->width;
    uint8 *src_u = src_y + src_stride_y * mI420->height;
    int src_stride_u = (mI420->width+1)/2;
    uint8 *src_v = src_u + src_stride_u * mI420->height/2;
    int src_stride_v = (mI420->width+1)/2;
    int src_width = mI420->width;
    int src_height = mI420->height;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int dst_width = i420->width;
    int dst_height = i420->height;
    
    int ret = libyuv::I420Scale(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, src_width, src_height, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, dst_width, dst_height, libyuv::kFilterBox);
    
    if (ret) return false;
    
    return true;
    
}



bool LibyuvColorSpaceConvert::NV21toI420(MediaStreamer::VideoFrame* nv21, MediaStreamer::VideoFrame* i420)
{
    i420->frameSize = nv21->frameSize;
    i420->pts = nv21->pts;
    
    uint8* src_y = nv21->data;
    int src_stride_y = nv21->width;
    uint8* src_vu = src_y + src_stride_y * nv21->height;
    int src_stride_vu = nv21->width;
    
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    
    int ret = libyuv::NV21ToI420(src_y, src_stride_y,
               src_vu, src_stride_vu,
               dst_y, dst_stride_y,
               dst_u, dst_stride_u,
               dst_v, dst_stride_v,
               i420->width, i420->height);
    
    if (ret) return false;
    
    return true;
}

bool LibyuvColorSpaceConvert::NV21toI420_Rotation(MediaStreamer::VideoFrame* nv21, MediaStreamer::VideoFrame* i420, int rotation)
{
    mI420->frameSize = nv21->frameSize;
    mI420->pts = nv21->pts;
    mI420->width = nv21->width;
    mI420->height = nv21->height;
    
    this->NV21toI420(nv21, mI420);
    
    uint8 *src_y = mI420->data;
    int src_stride_y = mI420->width;
    uint8 *src_u = src_y + src_stride_y * mI420->height;
    int src_stride_u = (mI420->width+1)/2;
    uint8 *src_v = src_u + src_stride_u * mI420->height/2;
    int src_stride_v = (mI420->width+1)/2;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int src_width = mI420->width;
    int src_height = mI420->height;
    
    i420->frameSize = mI420->frameSize;
    i420->pts = mI420->pts;
    
    int ret = libyuv::I420Rotate(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, src_width, src_height, libyuv::RotationMode(rotation));
    
    if (ret) return false;
    
    return true;
}

bool LibyuvColorSpaceConvert::NV21toI420_Crop_Rotation(MediaStreamer::VideoFrame* nv21, MediaStreamer::VideoFrame* i420, int rotation)
{
    i420->pts = nv21->pts;
    
    int i420_width_pre_rotation= 0;
    int i420_height_pre_rotation = 0;
    
    if (rotation==0 || rotation==180) {
        i420_width_pre_rotation = i420->width;
        i420_height_pre_rotation = i420->height;
    }else if(rotation==90 || rotation==270) {
        i420_width_pre_rotation = i420->height;
        i420_height_pre_rotation = i420->width;
    }
    
    if (nv21->width<i420_width_pre_rotation || nv21->height<i420_height_pre_rotation) {
        return false;
    }

    uint8* src_frame = nv21->data;
    size_t src_size = nv21->frameSize;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int crop_x;
    int crop_y;
    int src_width = nv21->width;
    int src_height = nv21->height;
    int crop_width;
    int crop_height;
    uint32 format = libyuv::FOURCC_NV21;
    
    if (rotation==0 || rotation==180) {
        crop_x = (nv21->width - i420->width)/2;
        crop_y = (nv21->height - i420->height)/2;
        
        crop_width = i420->width;
        crop_height = i420->height;
    }else if(rotation==90 || rotation==270) {
        crop_x = (nv21->width - i420->height)/2;
        crop_y = (nv21->height - i420->width)/2;
        
        crop_width = i420->height;
        crop_height = i420->width;
    }else {
        crop_x = 0;
        crop_y = 0;
        
        crop_width = src_width;
        crop_height = src_height;
    }
    
    int ret = libyuv::ConvertToI420(src_frame, src_size,
                      dst_y, dst_stride_y,
                      dst_u, dst_stride_u,
                      dst_v, dst_stride_v,
                      crop_x, crop_y,
                      src_width, src_height,
                      crop_width, crop_height,
                      libyuv::RotationMode(rotation),
                      format);
    
    if (ret) return false;
   
    return true;
}

bool LibyuvColorSpaceConvert::NV21toI420_Crop_Rotation_Scale(MediaStreamer::VideoFrame* nv21, MediaStreamer::VideoFrame* i420, int rotation)
{
    i420->pts = nv21->pts;
    
    int i420_width_pre_rotation= 0;
    int i420_height_pre_rotation = 0;
    
    if (rotation==0 || rotation==180) {
        i420_width_pre_rotation = i420->width;
        i420_height_pre_rotation = i420->height;
    }else if(rotation==90 || rotation==270) {
        i420_width_pre_rotation = i420->height;
        i420_height_pre_rotation = i420->width;
    }
    
//    if (nv21->width>=i420_width_pre_rotation && nv21->height>=i420_height_pre_rotation) {
//        return NV21toI420_Crop_Rotation(nv21, i420, rotation);
//    }
    
    if(nv21->width*i420_height_pre_rotation>=i420_width_pre_rotation*nv21->height)
    {
        if (rotation==0 || rotation==180) {
            mI420->width = i420_width_pre_rotation*nv21->height/i420_height_pre_rotation;
            mI420->height = nv21->height;
        }else if(rotation==90 || rotation==270) {
            mI420->width = nv21->height;
            mI420->height = i420_width_pre_rotation*nv21->height/i420_height_pre_rotation;
        }

    }else if(nv21->width*i420_height_pre_rotation<i420_width_pre_rotation*nv21->height)
    {
        if (rotation==0 || rotation==180) {
            mI420->width = nv21->width;
            mI420->height = i420_height_pre_rotation*nv21->width/i420_width_pre_rotation;
        }else if(rotation==90 || rotation==270) {
            mI420->width = i420_height_pre_rotation*nv21->width/i420_width_pre_rotation;
            mI420->height = nv21->width;
        }
    }
    mI420->width = mI420->width%2>0?mI420->width+1:mI420->width;
    mI420->height = mI420->height%2>0?mI420->height+1:mI420->height;
    
    mI420->frameSize = mI420->width*mI420->height*3/2;
    mI420->pts = nv21->pts;
    
    bool bRet = NV21toI420_Crop_Rotation(nv21, mI420, rotation);
    
    if (!bRet) return false;
    
    //do scale
    uint8 *src_y = mI420->data;
    int src_stride_y = mI420->width;
    uint8 *src_u = src_y + src_stride_y * mI420->height;
    int src_stride_u = (mI420->width+1)/2;
    uint8 *src_v = src_u + src_stride_u * mI420->height/2;
    int src_stride_v = (mI420->width+1)/2;
    int src_width = mI420->width;
    int src_height = mI420->height;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int dst_width = i420->width;
    int dst_height = i420->height;
    
    int ret = libyuv::I420Scale(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, src_width, src_height, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, dst_width, dst_height, libyuv::kFilterBox);
    
    if (ret) return false;
    
    return true;

}

bool LibyuvColorSpaceConvert::NV21toI420_Crop_Rotation_Scale_Mirror(MediaStreamer::VideoFrame* nv21, MediaStreamer::VideoFrame* i420, int rotation)
{
    i420->pts = nv21->pts;
    
    mI420UnMirror->width = i420->width;
    mI420UnMirror->height = i420->height;
    mI420UnMirror->frameSize = mI420UnMirror->width*mI420UnMirror->height*3/2;
    
    bool bRet = NV21toI420_Crop_Rotation_Scale(nv21, mI420UnMirror, rotation);
    if (!bRet) return false;
    
    uint8 *src_y = mI420UnMirror->data;
    int src_stride_y = mI420UnMirror->width;
    uint8 *src_u = src_y + src_stride_y * mI420UnMirror->height;
    int src_stride_u = (mI420UnMirror->width+1)/2;
    uint8 *src_v = src_u + src_stride_u * mI420UnMirror->height/2;
    int src_stride_v = (mI420UnMirror->width+1)/2;
    uint8* dst_y = i420->data;
    int dst_stride_y = i420->width;
    uint8* dst_u = dst_y + dst_stride_y * i420->height;
    int dst_stride_u = (i420->width+1)/2;
    uint8* dst_v = dst_u + dst_stride_u * i420->height/2;
    int dst_stride_v = (i420->width+1)/2;
    int width = i420->width;
    int height = i420->height;
    
    int ret = libyuv::I420Mirror(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height);
    
    if (ret) return false;
    
    return true;
}
